import { getLIS, isBoolean } from '../utils/utility'
import { ShapeFlags, normalizeVNode, queueJob } from '.'
import { reactive, effect } from '../reactive'

/**
 * @description: 渲染虚拟DOM
 * @param {VNode} vnode
 * @param {*} container
 * @return {*}
 */
export function render(vnode, container) {
    const prevNode = container._vnode

    // 如果vnode不存在而prevNode存在
    // 则卸载prevNode
    if (!vnode) {
        if (prevNode) {
            unmount(prevNode)
        }
    } else {
        patch(prevNode, vnode, container)
    }
    container._vnode = vnode
}

/**
 * @description: 将虚拟DOM从节点上卸载
 * @param {*} vnode
 * @return {*}
 */
function unmount(vnode) {
    const { shapeFlag, el } = vnode
    if (shapeFlag & ShapeFlags.COMPONENT) {
        unmountComponent(vnode)
    } else if (shapeFlag & ShapeFlags.FRAGMENT) {
        unmountFragment(vnode)
    } else {
        el.parentNode.removeChild(el)
    }
}

function unmountComponent(vnode) {
    // todo
}

function unmountFragment(vnode) {
    const { el, anchor } = vnode
    const parentNode = el.parentNode
    while (el !== anchor) {
        let next = el.nextSibling
        parentNode.removeChild(el)
        el = next
    }

    parentNode.removeChild(anchor)
}

function unmountChildren(children) {
    children.forEach((child) => {
        unmount(child)
    })
}

function processComponent(prevNode, vnode, container, anchor = null) {
    if (prevNode) {
        // TODO: 如果vnodeProps没有改变，那么与之关联的子组件也无需改变
        updateComponent(prevNode, vnode)
    } else {
        mountComponent(vnode, container, anchor)
    }
}

function processText(prevNode, vnode, container, anchor = null) {
    // 如果文本节点存在，复用节点
    if (prevNode) {
        vnode.el = prevNode.el
        prevNode.el.textContent = vnode.children
    } else {
        // 否则创建并挂载
        mountTextNode(vnode, container, anchor)
    }
}

function processFragment(prevNode, vnode, container, anchor = null) {
    // 首先需要对Fragment添加锚点
    const fragmentStartAnchor = (vnode.el = prevNode ? prevNode.el : document.createTextNode(""))
    const fragmentEndAnchor = (vnode.anchor = prevNode ? prevNode.anchor : document.createTextNode(""))

    // 如果片段节点存在，复用节点
    if (prevNode) {
        patchChildren(prevNode, vnode, container, fragmentEndAnchor)
    } else {
        // 否则创建并挂载
        container.insertBefore(fragmentStartAnchor, anchor)
        container.insertBefore(fragmentEndAnchor, anchor)
        mountChildren(vnode.children, container, fragmentEndAnchor)
    }
}

function processElement(prevNode, vnode, container, anchor = null) {
    // 如果元素节点存在，复用节点
    if (prevNode) {
        patchElement(prevNode, vnode)
    } else {
        // 否则创建并挂载
        mountElement(vnode, container, anchor)
    }
}

function mountElement(vnode, container, anchor = null) {
    const { type, props, shapeFlag, children } = vnode
    const el = document.createElement(type)
    patchProps(null, props, el)

    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
        mountTextNode(vnode, el)
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        mountChildren(children, el)
    }

    container.insertBefore(el, anchor)
    vnode.el = el
}

function mountTextNode(vnode, container, anchor = null) {
    const textNode = document.createTextNode(vnode.children)
    container.insertBefore(textNode, anchor)
    vnode.el = textNode
}

function mountChildren(children, container, anchor = null) {
    children.forEach((child) => {
        patch(null, child, container, anchor)
    })
}

function mountComponent(vnode, container, anchor = null) {
    const { type: Component } = vnode

    // 一个组件必有一个实例对应
    const instance = (vnode.component = {
        props: null,
        attrs: null,
        setupState: null,
        ctx: null,
        subTree: null,
        update: null,
        next: null
    })

    updateProps(instance, vnode)

    // composition api(https://v3.cn.vuejs.org/guide/composition-api-setup.html)
    // setup()
    // TODO: Vue源码中使用proxyRef进行代理
    // 使得在render函数中可以直接使用ctx.${prop}
    // 我们没有，因此必须使用ctx.${prop}.value
    instance.setupState = Component.setup?.(instance.props, { attrs: instance.attrs })

    // render(ctx)
    instance.ctx = {
        ...instance.props,
        ...instance.setupState
    }

    instance.update = effect(() => {
        if (instance.next) {
            // 被动更新
            vnode = instance.next
            instance.next = null

            // 主要更新props
            updateProps(instance, vnode)
            instance.ctx = {
                ...instance.props,
                ...instance.setupState
            }
        }

        const prev = instance.subTree
        // render函数的返回值多种多样
        // 可能是单纯的字符串、数字；也可能是h函数，数组
        // 所以我们需要预处理该结果
        const subTree = (instance.subTree = normalizeVNode(
            Component.render(instance.ctx)
        ))

        if (Object.keys(instance.attrs).length) {
            subTree.props = {
                ...subTree.props,
                ...instance.attrs
            }
        }

        patch(prev, subTree, container, anchor)
        vnode.el = subTree.el
    }, {
        scheduler: queueJob,
    })
}

function updateProps(instance, vnode) {
    const { type: component, props: vnodeProps } = vnode
    const props = instance.props = {}
    const attrs = instance.attrs = {}
    for (const key in vnodeProps) {
        // 出于简单起见，props当作数组处理
        // Vue3中可以接受对象，数组，定义默认值等
        if (component.props?.includes(key)) {
            props[key] = vnodeProps[key]
        } else {
            attrs[key] = vnodeProps[key]
        }
    }

    instance.props = reactive(instance.props)
}

function updateComponent(prevNode, vnode) {
    vnode.component = prevNode.component
    vnode.component.next = vnode
    vnode.component.update()
}

/**
 * @description: 更新节点上的虚拟DOM
 * @param {*} prevNode
 * @param {*} vnode
 * @param {*} container
 * @param {*} anchor default null
 * @return {*}
 */
function patch(prevNode, vnode, container, anchor = null) {
    // 若类型不同，卸载prevNode
    if (prevNode && !isSameNode(prevNode, vnode)) {
        anchor = (prevNode.anchor || prevNode.el).nextSibling
        unmount(prevNode)
        prevNode = null
    }

    const { shapeFlag } = vnode
    if (shapeFlag & ShapeFlags.COMPONENT) {
        processComponent(prevNode, vnode, container, anchor)
    } else if (shapeFlag & ShapeFlags.TEXT) {
        processText(prevNode, vnode, container, anchor)
    } else if (shapeFlag & ShapeFlags.FRAGMENT) {
        processFragment(prevNode, vnode, container, anchor)
    } else {
        processElement(prevNode, vnode, container, anchor)
    }
}

function isSameNode(n1, n2) {
    return n1.type === n2.type
}

function patchElement(prevNode, vnode) {
    vnode.el = prevNode.el
    patchProps(prevNode.props, vnode.props, vnode.el)
    patchChildren(prevNode, vnode, vnode.el)
}

/**
 * @description: 更新孩子节点，Text|Array|null，3x3九种情况
 * @param {*} prevNode
 * @param {*} vnode
 * @param {*} container
 * @param {*} anchor default null
 * @return {*}
 */
function patchChildren(prevNode, vnode, container, anchor = null) {
    const { shapeFlag: prevShapeFlag, children: prevChildren } = prevNode
    const { shapeFlag: vShapeFlag, children: vChildren } = vnode

    if (vShapeFlag & ShapeFlags.TEXT_CHILDREN) {
        if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            // 如果不同类型，先卸载原始节点
            unmountChildren(prevChildren)
        }
        // 更新文本
        if (prevChildren !== vChildren)
            container.textContent = vChildren
    } else if (vShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        // 卸载原始节点
        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
            container.textContent = ""
            mountChildren(vChildren, container, anchor)
        } else if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            // 假定要求一个孩子节点有key，所有节点必须有key
            if ((prevChildren[0] && prevChildren[0].key != null) && (vChildren[0] && vChildren[0].key != null)) {
                // 通过比较key，可以最大程度减少DOM操作
                patchKeyedChildren(prevChildren, vChildren, container, anchor)
            }
            else {
                patchUnkeyedChildren(prevChildren, vChildren, container, anchor)
            }
        } else {
            mountChildren(vChildren, container, anchor)
        }
    } else {
        // 卸载原始节点
        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
            container.textContent = ""
        } else if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            unmountChildren(prevChildren)
        }
    }
}

/**
 * @description: Diffing算法实现子元素更新(react version)
 * @param {*} prevChildren
 * @param {*} vChildren
 * @param {*} container
 * @param {*} anchor default null
 * @return {*}
 */
function patchKeyedChildren_react(prevChildren, vChildren, container, anchor = null) {
    // 记录最长上升子序列
    // 仅当旧位置序号小于上升序列时，移动
    let maxNewIndex = 0

    // 记录原始所有节点的key
    const map = new Map()
    prevChildren.forEach((prev, index) => {
        map.set(prev.key, { prev, index })
    })

    for (let i = 0; i < vChildren.length; i++) {
        const next = vChildren[i]
        const insertPos = i === 0 ? prevChildren[0].el : vChildren[i - 1].el.nextSibling
        // 如果是原有节点，考虑是否需要移动
        if (map.has(next.key)) {
            const { prev, index } = map.get(next.key)
            patch(prev, next, container, anchor)
            if (index < maxNewIndex) {
                // insertBefore对已有节点进行移动
                container.insertBefore(next.el, insertPos)
            } else {
                maxNewIndex = index
            }

            // 从map中删除更新了的节点
            map.delete(next.key)
        } else {
            // 如果是一个全新节点，则进行插入
            patch(null, next, container, insertPos)
        }
    }

    // 删除原有，现没有的节点
    map.forEach(({ prev }) => {
        unmount(prev)
    })
}

/**
 * @description: Diffing算法实现子元素更新(vue3 version)
 * @param {*} prevChildren
 * @param {*} vChildren
 * @param {*} container
 * @param {*} anchor default null
 * @return {*}
 */
function patchKeyedChildren(prevChildren, vChildren, container, anchor = null) {
    let i = 0
    let end1 = prevChildren.length - 1, end2 = vChildren.length - 1

    // 1. 从左至右依次比较
    while (i <= end1 && i <= end2 && prevChildren[i].key === vChildren[i].key) {
        patch(prevChildren[i], vChildren[i], container, anchor)
        i++
    }

    // 2. 从右至左依次比较
    while (i <= end1 && i <= end2 && prevChildren[end1].key === vChildren[end2].key) {
        patch(prevChildren[end1], vChildren[end2], container, anchor)
        end1--
        end2--
    }

    // 3. 若旧节点全部出现，则剩下的新节点直接mount
    if (i > end1) {
        for (let j = i; j <= end2; j++) {
            // 向最后一个“右匹配”之前加入
            const insertPos = vChildren[end2 + 1] ? vChildren[end2 + 1].el : anchor
            patch(null, vChildren[j], container, insertPos)
        }
    } else if (i > end2) {
        // 若新的节点全部出现，则卸载剩余的旧节点
        for (let j = i; j <= end1; j++) {
            unmount(prevChildren[j])
        }
    } else {
        // 4. 不满足3时，采用react diff算法
        // 但不进行添加移动，仅标记(source数组)和删除 

        // 记录最长上升子序列
        // 仅当旧位置序号小于上升序列时，移动
        let maxNewIndex = 0
        let move = false    // 标志是否需要移动操作

        // 记录原始所有节点的key
        const map = new Map()
        for (let k = i; k <= end1; k++) {
            const prev = prevChildren[k]
            map.set(prev.key, { prev, index: k })
        }

        const source = new Array(end2 - i + 1).fill(-1)
        const toMount = []

        for (let k = 0; k < source.length; k++) {
            const next = vChildren[i + k]

            // 如果是原有节点，考虑是否需要移动
            if (map.has(next.key)) {
                const { prev, index } = map.get(next.key)
                patch(prev, next, container, anchor)
                if (index < maxNewIndex) {
                    move = true
                } else {
                    maxNewIndex = index
                }
                source[k] = index
                // 从map中删除更新了的节点
                map.delete(next.key)
            } else {
                // 如果是一个全新节点，则进行插入
                toMount.push(i + k)
            }
        }

        // 删除原有，现没有的节点
        map.forEach(({ prev }) => {
            unmount(prev)
        })

        // 5. 需要移动，“最长上升子序列”算法
        // 在该序列以内的元素不需要移动，以外的需要
        if (move) {
            const seq = getLIS(source)   // 获取最长上升序列
            let end = seq.length - 1
            // 从后往前遍历
            for (let j = source.length - 1; j >= 0; j--) {
                const nodePos = i + j
                const nextPos = nodePos + 1
                const insertPos = vChildren[nextPos] ? vChildren[nextPos].el : anchor

                // 新节点，挂载
                if (source[j] === -1) {
                    patch(null, vChildren[nodePos], container, insertPos)
                } else if (seq[end] === j) {
                    // 不需要移动
                    end--
                } else {
                    // 移动
                    container.insertBefore(vChildren[nodePos].el, insertPos)
                }
            }
        } else if (toMount.length) {
            for (let k = toMount.length - 1; k >= 0; k--) {
                const nodePos = toMount[k]
                const nextPos = nodePos + 1
                const insertPos = vChildren[nextPos] ? vChildren[nextPos].el : anchor
                patch(null, vChildren[nodePos], container, insertPos)
            }
        }
    }
}

function patchUnkeyedChildren(prevChildren, vChildren, container, anchor = null) {
    const oldLength = prevChildren.length
    const newLength = vChildren.length
    const commonLength = Math.min(oldLength, newLength)

    // 对于公共长度之内的子元素，直接patch更新
    for (let i = 0; i < commonLength; i++) {
        patch(prevChildren[i], vChildren[i], container, anchor)
    }

    // 如果原有的元素更多，删除多于元素
    if (oldLength > newLength) {
        unmountChildren(prevChildren.slice(commonLength))
    }
    else if (newLength > oldLength) {
        // 如果新的元素更多，挂载
        mountChildren(vChildren.slice(commonLength), container, anchor)
    }
}

function patchProps(prevProps, Props, el) {
    if (prevProps === Props) {
        return
    }

    prevProps = prevProps || {}
    Props = Props || {}

    // 添加Props中的新属性
    for (let key in Props) {
        if (key === "key")
            continue

        const next = Props[key]
        const prev = prevProps[key]
        if (prev !== next)
            patchDomProp(prev, next, key, el)
    }

    // 删除prevProps有，Props没有的属性
    for (let key in prevProps) {
        if (key !== "key" && Props[key] == null)
            patchDomProp(prevProps[key], null, key, el)
    }
}

// 在DOM中Properties和Attribute的赋予方式不同
// 有几个特定的属性需要以Properties方式挂载
// 因此使用正则来提取
const domPropsRE = /[A-Z]|^(value|checked|selected|muted|disabled)$/

function patchDomProp(prev, next, key, el) {
    switch (key) {
        case 'class':
            // || ""，防止为null
            el.className = next || ""
            break;
        case 'style':
            // 清空原本的style
            if (prev)
                el.style = {}

            // 更新style
            for (let styleName in next) {
                el.style[styleName] = next[styleName]
            }
            break;
        default:
            // 处理响应函数，使用正则表达式
            // 规则：on开头，函数名首字母大写，如onClick
            if (/^on[A-Z]/.test(key)) {
                const eventName = key.slice(2).toLowerCase()
                if (prev) {
                    el.removeEventListener(eventName, prev)
                }
                if (next) {
                    el.addEventListener(eventName, next)
                }
            } else if (domPropsRE.test(key)) {
                if (next === "" && isBoolean(el[key])) {
                    next = true
                }
                el[key] = next
            } else {
                // 注意：setAttribute会以string类型为属性赋值
                // 即false会被视为"false"
                // 这不符合我们的需求，因此要特殊处理
                if (next == null || next === false) {
                    el.removeAttribute(key)
                } else {
                    el.setAttribute(key, next)
                }
            }
    }
}